<!DOCTYPE html>
<html lang="en">

<head>
    <!-- <meta http-equiv="Content-Type" content="text/html" /> -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>nums[i] - next(nums)[i]</title>
</head>
<style>
    :root {
        --LINE_HEIGHT: 1.4rem;
        --C_0: #555;
        --C_1: #dd9;
        --C_2: #aa2;
        --C_3: #9dd;
        --C_4: #2aa;
        --C_5: #d9d;
        --C_6: #a2a;
        --C_7: #f49;
        --C_8: #94f;
        --C_9: #ada;
        --C_A: #49f;
        --C_B: #f94;
        --C_C: #2d9;
        --C_D: #99f;
        --C_E: #f99;
        --C_F: #9f4;
    }

    body {
        font-family: Consolas, "Courier New", monospace;
        font-size: 24px;
        min-width: 1080px;
        background-color: #333;
        color: #fff;
    }

    header {
        width: 100%;

        p {
            margin-block-start: 1.2rem;
            margin-block-end: 1.2rem;
            text-align: center;
            font-size: 2rem;
            line-height: 2rem;
            font-weight: bolder;

            input {
                font-size: 2rem;
                line-height: 2rem;
                text-align: center;
                border-radius: 0.4rem;

                &#iptN {
                    width: 36rem;
                    letter-spacing: 0.4rem;
                }
            }
        }
    }

    main {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: var(--LINE_HEIGHT);

        margin: 0 auto;
        min-height: var(--LINE_HEIGHT);
        min-width: 1080px;

        section {
            display: grid-row;
            width: 1fr;
            text-align: right;

            div {
                padding: 0 calc(var(--LINE_HEIGHT) / 2);

                &:nth-child(2n+1) {
                    background-color: #433;
                }

                &:nth-child(2n) {
                    background-color: #343;
                }


            }

            &#col2 {
                text-align: left;
                padding-top: calc(var(--LINE_HEIGHT) * 0.5);
            }

            /* &#col3 {
                padding-top: calc(var(--LINE_HEIGHT) * 1.5);

                span {
                    width: calc(var(--LINE_HEIGHT) * 0.8);
                }
            } */

        }

        span {
            display: inline-block;
            color: transparent;
            width: calc(var(--LINE_HEIGHT)*0.64);
            line-height: var(--LINE_HEIGHT);

            &[v="0"] {
                &::before {
                    content: "0";
                    color: var(--C_0);
                }
            }

            &[v="1"] {
                &::before {
                    content: "1";
                    color: var(--C_1);
                }
            }

            &[v="2"] {
                &::before {
                    content: "2";
                    color: var(--C_2);
                }
            }

            &[v="3"] {
                &::before {
                    content: "3";
                    color: var(--C_3);
                }
            }

            &[v="4"] {
                &::before {
                    content: "4";
                    color: var(--C_4);
                }
            }

            &[v="5"] {
                &::before {
                    content: "5";
                    color: var(--C_5);
                }
            }

            &[v="6"] {
                &::before {
                    content: "6";
                    color: var(--C_6);
                }
            }

            &[v="7"] {
                &::before {
                    content: "7";
                    color: var(--C_7);
                }
            }

            &[v="8"] {
                &::before {
                    content: "8";
                    color: var(--C_8);
                }
            }

            &[v="9"] {
                &::before {
                    content: "9";
                    color: var(--C_9);
                }
            }

            &[v="A"] {
                &::before {
                    content: "A";
                    color: var(--C_A);
                }
            }

            &[v="B"] {
                &::before {
                    content: "B";
                    color: var(--C_B);
                }
            }

            &[v="C"] {
                &::before {
                    content: "C";
                    color: var(--C_C);
                }
            }

            &[v="D"] {
                &::before {
                    content: "D";
                    color: var(--C_D);
                }
            }

            &[v="E"] {
                &::before {
                    content: "E";
                    color: var(--C_E);
                }
            }

            &[v="F"] {
                &::before {
                    content: "F";
                    color: var(--C_F);
                }
            }
        }

    }

    #VerticalLine {
        position: absolute;
        top: 0;
        height: 100%;
        width: 2px;
        background-color: white;
        pointer-events: none;
        /* Ensure the line does not interfere with mouse events */
    }
</style>

<body>
    <header>
        <p>
            <input type="number" id="iptN" placeholder="Input Hex Number"></input>
            <input type="number" id="iptS" value="2" min="2" max="6" placeholder="Step"></input>
            <input type="text" id="iptF" placeholder="Input a function" value="return a ^ b"></input>
            <span id="CharSize">0</span>
        </p>
    </header>

    <main id="cols">
        <section id="col1"> </section>
        <section id="col2"> </section>
    </main>

    <div id="VerticalLine"></div>

</body>


<script type="module">
    "use strict";
    const NEXT_MAP = {
        0: { 0: 0, 4: 0, 1: 4, 5: 4, 3: 1, 7: 1, 8: 8 },
        1: { 0: 0, 4: 0, 1: 4, 5: 4, 3: 1, 7: 1, 8: 8 },
        3: { 0: 0, 4: 0, 1: 4, 5: 4, 3: 1, 7: 1, 8: 8 },
        4: { 0: 5, 4: 5, 1: 3, 5: 3, 3: 7, 7: 7, 8: 3 },
        5: { 0: 5, 4: 5, 1: 3, 5: 3, 3: 7, 7: 7, 8: 3 },
        7: { 0: 5, 4: 5, 1: 3, 5: 3, 3: 7, 7: 7, 8: 3 },
        8: { 0: -1, 4: -1, 1: -1, 5: -1, 3: -1, 7: -1, 8: 8 },
    };
    const MAX_LEN = 16;
    const NUM_SCALE = 1 << 4;
    const L_P = 2 * MAX_LEN - 1;

    const nx = (p, n) => NEXT_MAP[p]?.[n] ?? -1;

    // local define
    const next = (nums) => {
        let num2 = [];
        for (let i = 0; i < nums.length - 1; i++) {
            num2.push(nx(nums[i], nums[i + 1]))
        }
        return [0, ...num2];
    }

    const toScaleUpper = (n) => ((~~n + NUM_SCALE) % NUM_SCALE).toString(NUM_SCALE).toUpperCase();

    // 节流
    const throttle = (func, limit) => {
        let last = 0;      // 上次执行时间
        let timer = null;  // 定时器
        return (...args) => {
            const now = Date.now();
            const remaining = limit - (now - last);

            if (remaining <= 0) {
                // 超过间隔，立即执行
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
                func.apply(this, args);
                last = now;
            } else if (!timer) {
                // 未超过间隔且无定时器，设置定时器延迟执行
                timer = setTimeout(() => {
                    func.apply(this, args);
                    last = Date.now();
                    timer = null;
                }, remaining);
            }
        };
    };

    var STEP = 2;
    var FUNC = (a, b) => a ^ b;
    var CACHES = [];
    var CharSize = new Set();

    // console.time("mousemove");
    document.body.addEventListener("mousemove", throttle((event) => {
        // console.timeLog("mousemove");
        document.getElementById("VerticalLine").style.left = `${event.clientX}px`
    }, 200));


    const col1 = document.getElementById("col1");
    const col2 = document.getElementById("col2");
    const VTL = document.getElementById("VerticalLine");
    const ChSz = document.getElementById("CharSize");

    

    document.getElementById("iptS").addEventListener("change", (evn) => {
        STEP = parseInt(evn.target.value);
        if (!(STEP >= 2 && STEP <= 6)) {
            // NaN > 0 === false;NaN < 0 === false;
            evn.target.value = STEP = 2;
            alert("Out of Range");
        }
    });

    document.getElementById("iptF").addEventListener("change", (evn) => {
        try {
            FUNC = new Function("a", "b", evn.target.value);
        } catch (e) {
            console.error("Error Function input", e);
            FUNC = null;
            return alert("Error Function");
        }

        if (FUNC) {
            if (FUNC.length !== 2) {
                console.error("Func must have 2 arguments", FUNC.toString());
                FUNC = null;
                return alert("Error Function");
            }
            console.log(FUNC.toString());
        }
    })

    document.getElementById("iptN").addEventListener("change", (evn) => {
        if (!FUNC) return alert("Please input a function");

        let nums = evn.target.value.toString().trim().split('');
        if (nums.length === 0 || nums.length > MAX_LEN) return alert("Out of Range");
        nums = nums.map(e => parseInt(e));
        if (nums.some(n => isNaN(n) || n < 0 || n > 8)) return alert("Error Input");

        /** NOTICE ! ! ! :  2 -> 7 ; 6 -> 8 */
        for (let i = 0; i < nums.length; i++) {
            if (nums[i] === 2) nums[i] = 7;
            if (nums[i] === 6) nums[i] = 8;
        }

        evn.target.value = nums.join('');

        col1.replaceChildren();
        col2.replaceChildren();

        CharSize.clear();

        CACHES = new Array(STEP);
        CACHES[STEP - 1] = [0, ...Array.from({ length: L_P + 1 }, (_, i) => (i < nums.length ? nums[i] : 8))];

        while (CACHES[STEP - 1][L_P] === 8) {

            for (let i = 0; i <= STEP - 2; i++) {
                CACHES[i] = !CACHES[i + 1] ? [] : [...CACHES[i + 1]];
            }
            CACHES[STEP - 1] = next(CACHES[STEP - 2]);

            let div1 = document.createElement("div");
            let div2 = document.createElement("div");

            for (let i = 0; i <= L_P; i++) {
                let span1 = document.createElement("span");
                span1.setAttribute("v", CACHES[STEP - 2][i]);
                span1.textContent = CACHES[STEP - 2][i];
                div1.appendChild(span1);
            }

            if (!CACHES[0]?.length) {
                let span2 = document.createElement("span");
                span2.textContent = " ";
                div2.appendChild(span2);
            } else {
                for (let i = 0; i <= L_P; i++) {
                    let nt = toScaleUpper(FUNC(CACHES[0][i], CACHES[STEP - 1][i]));
                    CharSize.add(nt);
                    let span2 = document.createElement("span");
                    span2.setAttribute("v", nt);
                    span2.textContent = nt;
                    div2.appendChild(span2);
                }
            }

            col1.appendChild(div1);
            col2.appendChild(div2);

        }

        ChSz.textContent = [...CharSize].join('');
        VTL.style.height = document.body.clientHeight + "px";
    });
</script>